#include "stdafx.h"
#include "crMD5Engine.h"
#include <fstream>

NS_CR_BEGIN

namespace utils
{
	namespace md5
	{

#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21

#define F(x, y, z) ((x & y) | (~x & z))
#define G(x, y, z) ((x & z) | (y & ~z))
#define H(x, y, z) (x ^ y ^ z)
#define I(x, y, z) (y ^ (x | ~z))

#define ROTATE_LEFT(x, n) ((x << n) | (x >> (32 - n)))
#define FF(a, b, c, d, x, s, ac)  \
	{                             \
		a += F(b, c, d) + x + ac; \
		a = ROTATE_LEFT(a, s);    \
		a += b;                   \
	}
#define GG(a, b, c, d, x, s, ac)  \
	{                             \
		a += G(b, c, d) + x + ac; \
		a = ROTATE_LEFT(a, s);    \
		a += b;                   \
	}
#define HH(a, b, c, d, x, s, ac)  \
	{                             \
		a += H(b, c, d) + x + ac; \
		a = ROTATE_LEFT(a, s);    \
		a += b;                   \
	}
#define II(a, b, c, d, x, s, ac)  \
	{                             \
		a += I(b, c, d) + x + ac; \
		a = ROTATE_LEFT(a, s);    \
		a += b;                   \
	}

		const uint8_t PADDING[64] = {
			0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
			0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
		};

		INLINE void md5_decode(uint32_t* output, const uint8_t* input, uint32_t len)
		{
			uint32_t i, j;

			for (i = 0, j = 0; j < len; i++, j += 4)
			{
				output[i] = ((uint32_t)input[j]) | ((uint32_t)input[j + 1] << 8) | ((uint32_t)input[j + 2] << 16) | ((uint32_t)input[j + 3] << 24);
			}
		}

		INLINE void md5_encode(uint8_t* output, uint32_t* input, uint32_t len)
		{
			uint32_t i, j;

			for (i = 0, j = 0; j < len; i++, j += 4)
			{
				output[j] = (uint8_t)(input[i] & 0xff);
				output[j + 1] = (uint8_t)((input[i] >> 8) & 0xff);
				output[j + 2] = (uint8_t)((input[i] >> 16) & 0xff);
				output[j + 3] = (uint8_t)((input[i] >> 24) & 0xff);
			}
		}

		INLINE void md5_transform(uint32_t state[4], const uint8_t block[64])
		{
			uint32_t a = state[0],
					 b = state[1],
					 c = state[2],
					 d = state[3];
			uint32_t x[16];

			md5_decode(x, block, 64);

			FF(a, b, c, d, x[0], 7, 0xd76aa478); //32λ����
			FF(d, a, b, c, x[1], 12, 0xe8c7b756);
			FF(c, d, a, b, x[2], 17, 0x242070db);
			FF(b, c, d, a, x[3], 22, 0xc1bdceee);
			FF(a, b, c, d, x[4], 7, 0xf57c0faf);
			FF(d, a, b, c, x[5], 12, 0x4787c62a);
			FF(c, d, a, b, x[6], 17, 0xa8304613);
			FF(b, c, d, a, x[7], 22, 0xfd469501);
			FF(a, b, c, d, x[8], 7, 0x698098d8);
			FF(d, a, b, c, x[9], 12, 0x8b44f7af);
			FF(c, d, a, b, x[10], 17, 0xffff5bb1);
			FF(b, c, d, a, x[11], 22, 0x895cd7be);
			FF(a, b, c, d, x[12], 7, 0x6b901122);
			FF(d, a, b, c, x[13], 12, 0xfd987193);
			FF(c, d, a, b, x[14], 17, 0xa679438e);
			FF(b, c, d, a, x[15], 22, 0x49b40821);

			GG(a, b, c, d, x[1], 5, 0xf61e2562);
			GG(d, a, b, c, x[6], 9, 0xc040b340);
			GG(c, d, a, b, x[11], 14, 0x265e5a51);
			GG(b, c, d, a, x[0], 20, 0xe9b6c7aa);
			GG(a, b, c, d, x[5], 5, 0xd62f105d);
			GG(d, a, b, c, x[10], 9, 0x2441453);
			GG(c, d, a, b, x[15], 14, 0xd8a1e681);
			GG(b, c, d, a, x[4], 20, 0xe7d3fbc8);
			GG(a, b, c, d, x[9], 5, 0x21e1cde6);
			GG(d, a, b, c, x[14], 9, 0xc33707d6);
			GG(c, d, a, b, x[3], 14, 0xf4d50d87);
			GG(b, c, d, a, x[8], 20, 0x455a14ed);
			GG(a, b, c, d, x[13], 5, 0xa9e3e905);
			GG(d, a, b, c, x[2], 9, 0xfcefa3f8);
			GG(c, d, a, b, x[7], 14, 0x676f02d9);
			GG(b, c, d, a, x[12], 20, 0x8d2a4c8a);

			HH(a, b, c, d, x[5], 4, 0xfffa3942);
			HH(d, a, b, c, x[8], 11, 0x8771f681);
			HH(c, d, a, b, x[11], 16, 0x6d9d6122);
			HH(b, c, d, a, x[14], 23, 0xfde5380c);
			HH(a, b, c, d, x[1], 4, 0xa4beea44);
			HH(d, a, b, c, x[4], 11, 0x4bdecfa9);
			HH(c, d, a, b, x[7], 16, 0xf6bb4b60);
			HH(b, c, d, a, x[10], 23, 0xbebfbc70);
			HH(a, b, c, d, x[13], 4, 0x289b7ec6);
			HH(d, a, b, c, x[0], 11, 0xeaa127fa);
			HH(c, d, a, b, x[3], 16, 0xd4ef3085);
			HH(b, c, d, a, x[6], 23, 0x4881d05);
			HH(a, b, c, d, x[9], 4, 0xd9d4d039);
			HH(d, a, b, c, x[12], 11, 0xe6db99e5);
			HH(c, d, a, b, x[15], 16, 0x1fa27cf8);
			HH(b, c, d, a, x[2], 23, 0xc4ac5665);

			II(a, b, c, d, x[0], 6, 0xf4292244);
			II(d, a, b, c, x[7], 10, 0x432aff97);
			II(c, d, a, b, x[14], 15, 0xab9423a7);
			II(b, c, d, a, x[5], 21, 0xfc93a039);
			II(a, b, c, d, x[12], 6, 0x655b59c3);
			II(d, a, b, c, x[3], 10, 0x8f0ccc92);
			II(c, d, a, b, x[10], 15, 0xffeff47d);
			II(b, c, d, a, x[1], 21, 0x85845dd1);
			II(a, b, c, d, x[8], 6, 0x6fa87e4f);
			II(d, a, b, c, x[15], 10, 0xfe2ce6e0);
			II(c, d, a, b, x[6], 15, 0xa3014314);
			II(b, c, d, a, x[13], 21, 0x4e0811a1);
			II(a, b, c, d, x[4], 6, 0xf7537e82);
			II(d, a, b, c, x[11], 10, 0xbd3af235);
			II(c, d, a, b, x[2], 15, 0x2ad7d2bb);
			II(b, c, d, a, x[9], 21, 0xeb86d391);

			state[0] += a;
			state[1] += b;
			state[2] += c;
			state[3] += d;

			// Zeroize sensitive information.
			memset(x, 0, sizeof(x));
		}

		crfk::tstring digest_memory(void* memchunk, int len)
		{
			md5_engine engine;
			return string(engine.digest_memory(memchunk, len)).tstr();
		}

		crfk::tstring digest_string(const tstring& str)
		{
			string tmp = str.mstr();
			md5_engine engine;
			return string(engine.digest_memory((void*)tmp.data(), tmp.length())).tstr();
		}

		CR_DLL tstring digest_file(const tstring& filename)
		{
			string tmp = filename.mstr();
			md5_engine engine;
			return string(engine.digest_file(tmp.c_str())).tstr();
		}
	}

	md5_engine::md5_engine()
	{
		init();
	}

	void md5_engine::init()
	{
		context.count[0] = context.count[1] = 0;

		context.state[0] = 0x67452301;
		context.state[1] = 0xefcdab89;
		context.state[2] = 0x98badcfe;
		context.state[3] = 0x10325476;

		memset(context.buffer, 0, sizeof(context.buffer));
		memset(_digest_chars, 0, sizeof(_digest_chars));
	}

	void md5_engine::update(const uint8_t* input, uint32_t input_len)
	{
		uint32_t i, index, part_len;

		index = ((context.count[0] >> 3) & 0x3f);
		part_len = 64 - index;
		context.count[0] += input_len << 3;
		if (context.count[0] < (input_len << 3))
			context.count[1]++;
		context.count[1] += input_len >> 29;

		if (input_len >= part_len)
		{
			memcpy(&context.buffer[index], input, part_len);
			md5::md5_transform(context.state, context.buffer);

			for (i = part_len; i + 63 < input_len; i += 64)
			{
				md5::md5_transform(context.state, &input[i]);
			}

			index = 0;
		}
		else
		{
			i = 0;
		}

		memcpy((uint8_t*)&context.buffer[index], &input[i], input_len - i);
	}

	void md5_engine::final()
	{
		uint8_t bits[8] = { 0 };
		uint32_t index, pad_len;

		index = ((context.count[0] >> 3) & 0x3f);
		pad_len = (index < 56) ? (56 - index) : (120 - index);

		md5::md5_encode(bits, context.count, 8);

		update(md5::PADDING, pad_len);

		update(bits, 8);

		md5::md5_encode(_digest_raw, context.state, 16);

		memset(&context, 0, sizeof(context));

		to_string();
	}

	void md5_engine::to_string()
	{
		for (size_t i = 0; i < 16; i++)
		{
#if _MSC_VER > 1600
			sprintf_s(_digest_chars + (i * 2), 33, "%02x", _digest_raw[i]);
#else
			sprintf(_digest_chars + (i * 2), "%02x", _digest_raw[i]);
#endif
		}
	}

	char* md5_engine::digest_memory(void* memchunk, int len)
	{
		init();
		update((const uint8_t*)memchunk, len);
		final();

		return _digest_chars;
	}

	char* md5_engine::digest_file(const char* filename)
	{
		init();

		_STD ifstream in;
		in.open(filename, _STD ios::binary | _STD ios::in);
		if (!in.good())
		{
			throw(_STD exception("cannot open file"));
			return _digest_chars;
		}
		else
		{
			int32_t len;
			uint8_t buffer[1024];
			do
			{
				in.read((char*)buffer, 1024);
				len = (int32_t)in.gcount();
				update(buffer, len);
			} while (!in.eof());
			final();
			in.close();
		}
		return _digest_chars;
	}
}

NS_CR_END